Skip to content

fix(super-editor): stop export from stripping tracked marks off the caller's tree - #3949

Open
leonidkuznetsov18 wants to merge 1 commit into
superdoc:v1from
leonidkuznetsov18:fix-3893-header-export-strips-tracked-marks
Open

fix(super-editor): stop export from stripping tracked marks off the caller's tree#3949
leonidkuznetsov18 wants to merge 1 commit into
superdoc:v1from
leonidkuznetsov18:fix-3893-header-export-strips-tracked-marks

Conversation

@leonidkuznetsov18

@leonidkuznetsov18 leonidkuznetsov18 commented Aug 31, 2026

Copy link
Copy Markdown

What

Once a header (or footer) sub-editor exists, the first exportDocx() writes the part's imported w:ins/w:del correctly and every later export writes it with the tracked changes gone — text inside a w:del comes back as an ordinary run, so a counterparty's tracked deletion silently becomes accepted content on the second save, with no user edit involved (#3893).

Mechanism

The w:ins/w:del decoders (v3/handlers/w/ins/ins-translator.js, v3/handlers/w/del/del-translator.js) removed the tracked-change mark by assigning the filtered marks array back onto the node they were handed:

node.marks = marks.filter((m) => m.type !== 'trackInsert');

That node belongs to the caller. The body path never notices because it starts from a fresh getUpdatedJson() snapshot each export, but #exportProcessHeadersFooters passes the converter's persistent import-time tree (this.headers[id] / this.footers[id]) by reference once a sub-editor is registered — which happens as soon as a user clicks into the header. Export 1 strips the marks off that tree permanently; export 2 re-serializes the already-stripped tree.

Fix

Strip the mark on a shallow copy of the node instead ({ ...node, marks: filtered }), leaving the caller's tree intact. This fixes the root cause for every exportToXmlJson caller, including the header-footer-sync fallback path that also passes the persistent tree by reference — repeated exports with no intervening edit now produce the same tracked changes every time. These two assignments are the only in-place mutations of caller data on the export path (audited node.marks = / node.attrs = / splice sites in the decode chain).

Testing

  • New integration regression: headerTrackedChangesDoubleExport.test.js with a synthetic fixture (header-tracked-changes.docx, derived from the in-tree header-alternating-odd-even.docx with one w:ins + one w:del spliced into word/header1.xml). It registers a header sub-editor the way HeaderFooterEditorManager does, exports twice, and asserts word/header1.xml still carries ins=1 del=1 on the second export. Red without the fix (second export drops to ins=0 del=0, exactly the issue's output), green with it.
  • New unit tests in both translator suites assert decode leaves the caller's marks untouched while the stripped copy is what reaches exportSchemaToJson.
  • Full import-export + super-converter suites: 3643/3643 pass.

Fixes #3893

Review in cubic

…aller's tree

The w:ins/w:del decoders removed the tracked-change mark from the node they
were handed by assigning the filtered marks array back onto it. That node
belongs to the caller: the body export starts from a fresh getUpdatedJson()
snapshot each time, but once a header/footer sub-editor is registered,
#exportProcessHeadersFooters serializes the converter's persistent
import-time tree (this.headers[id] / this.footers[id]) by reference. The
first exportDocx() therefore stripped trackInsert/trackDelete permanently,
and every later export wrote the header/footer redline as accepted plain
text — a counterparty's tracked deletion silently became regular content on
the second save, with no user edit involved.

Strip the mark on a shallow copy of the node instead, leaving the caller's
tree intact, so repeated exports with no intervening edit produce the same
tracked changes every time.

Fixes superdoc#3893

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 6 files

Re-trigger cubic

@leonidkuznetsov18

Copy link
Copy Markdown
Author

recheck

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant